/*
 * Copyright (c) 2005, 2014 vacoor
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 */
package org.vacoor.nothing.web.dao.support.hbn.util;

import org.hibernate.criterion.*;
import org.vacoor.nothing.web.domain.Filters;
import org.vacoor.nothing.web.domain.Sort;

import java.util.LinkedHashMap;
import java.util.Map;

/**
 * Cascade.from(dc).eq("decode.b.c.d", 123).allEq(params).filter(filter).over();
 * 当调用 over() 将生成的别名写入
 * <p/>
 * 级联属性别名生成
 * eg: classes.student.name --> root_entity, root_entity_classes, root_entity_student 别名(Hibernate需要)
 * <p/>
 * 使用 Hibernate 进行级联查询时候必须使用别名(!!!!!!且同一关联路径, 不能有多个别名!!!!!!, m(1..m):n(n>1) 时级联不能超过4级好像)
 * eg:
 * HQL:
 * FROM roles AS _role INNER JOIN _role.permissions AS _perm WHERE _perm.id = 1001
 * <p/>
 * Criteria / DetachedCriteria:
 * criteria.createAlias("roles", "_role").createAlias("_role.permission", "_perm").add(Restriction.eq("_perm.id", 1001L);
 *
 * @author vacoor
 * @see org.hibernate.Criteria#createAlias(String, String)
 * @see org.hibernate.criterion.DetachedCriteria#createAlias(String, String)
 */
public class Cascade {
    private final DetachedCriteria detachedCriteria;
    private final Map<String, String> allAlias;

    public static Cascade from(Class<?> entityType) {
        return from(DetachedCriteria.forClass(entityType));
    }

    public static Cascade from(DetachedCriteria dc) {
        return new Cascade(dc);
    }

    private Cascade(DetachedCriteria detachedCriteria) {
        this.detachedCriteria = detachedCriteria;
        allAlias = new LinkedHashMap<String, String>();
    }

    public Cascade eq(final String propertyPath, final Object value) {
        add(Restrictions.eq(aliasFor(propertyPath), value));
        return this;
    }

    public Cascade ne(final String propertyPath, final Object value) {
        add(Restrictions.ne(aliasFor(propertyPath), value));
        return this;
    }

    public Cascade gt(final String propertyPath, final Object value) {
        add(Restrictions.gt(aliasFor(propertyPath), value));
        return this;
    }

    public Cascade lt(final String propertyPath, final Object value) {
        add(Restrictions.lt(aliasFor(propertyPath), value));
        return this;
    }

    public Cascade ge(final String propertyPath, final Object value) {
        add(Restrictions.ge(aliasFor(propertyPath), value));
        return this;
    }

    public Cascade le(final String propertyPath, final Object value) {
        add(Restrictions.le(aliasFor(propertyPath), value));
        return this;
    }

    public Cascade like(final String propertyPath, final Object value) {
        add(Restrictions.like(aliasFor(propertyPath), value));
        return this;
    }

    public Cascade ilike(final String propertyPath, final Object value) {
        add(Restrictions.ilike(aliasFor(propertyPath), value));
        return this;
    }

    public Cascade in(final String propertyPath, final Object... values) {
        add(Restrictions.in(aliasFor(propertyPath), values));
        return this;
    }

    public Cascade isNull(final String propertyPath) {
        add(Restrictions.isNull(aliasFor(propertyPath)));
        return this;
    }

    public Cascade isNotNull(final String propertyPath) {
        add(Restrictions.isNotNull(aliasFor(propertyPath)));
        return this;
    }

    public Cascade isEmpty(final String propertyPath) {
        add(Restrictions.isEmpty(aliasFor(propertyPath)));
        return this;
    }

    public Cascade isNotEmpty(final String propertyPath) {
        add(Restrictions.isNotEmpty(aliasFor(propertyPath)));
        return this;
    }

    public Cascade idEq(Object value) {
        add(Restrictions.idEq(value));
        return this;
    }

    public Cascade allEq(final Map<String, ?> params) {
        if (params != null) {
            for (Map.Entry<String, ?> param : params.entrySet()) {
                String prop = aliasFor(param.getKey());
                add(Restrictions.eq(prop, param.getValue()));
            }
        }
        return this;
    }

    public Cascade eqProperty(String propertyPath, String otherPropertyPath) {
        add(Restrictions.eqProperty(aliasFor(propertyPath), aliasFor(otherPropertyPath)));
        return this;
    }

    public Cascade neProperty(String propertyPath, String otherPropertyPath) {
        add(Restrictions.neProperty(aliasFor(propertyPath), aliasFor(otherPropertyPath)));
        return this;
    }

    public Cascade gtProperty(String propertyPath, String otherPropertyPath) {
        add(Restrictions.gtProperty(aliasFor(propertyPath), aliasFor(otherPropertyPath)));
        return this;
    }

    public Cascade ltProperty(String propertyPath, String otherPropertyPath) {
        add(Restrictions.ltProperty(aliasFor(propertyPath), aliasFor(otherPropertyPath)));
        return this;
    }

    public Cascade geProperty(String propertyPath, String otherPropertyPath) {
        add(Restrictions.geProperty(aliasFor(propertyPath), aliasFor(otherPropertyPath)));
        return this;
    }

    public Cascade leProperty(String propertyPath, String otherPropertyPath) {
        add(Restrictions.leProperty(aliasFor(propertyPath), aliasFor(otherPropertyPath)));
        return this;
    }

    public Cascade filter(final Filters filters) {
        if (filters != null) {
            for (Filters.Filter item : filters) {
                Disjunction disjunction = Restrictions.disjunction();
                for (Object v : item) {
                    Criterion c = buildCriterion(aliasFor(item.getProperty()), item.getOperator(), v);
                    disjunction.add(c);
                }
                add(disjunction);
            }
        }
        return this;
    }

    public Cascade sort(final Sort sort) {
        if (sort != null) {
            for (Sort.Order order : sort) {
                String prop = aliasFor(order.getProperty());
                Sort.Direction dir = order.getDirection();
                addOrder(Sort.Direction.DESC == dir ? Order.desc(prop) : Order.asc(prop));
            }
        }
        return this;
    }

    /**
     * 将所有的需要生成的关联别名写入
     *
     * @return
     */
    public DetachedCriteria over() {
        for (Map.Entry<String, String> alias : allAlias.entrySet()) {
            detachedCriteria.createAlias(alias.getKey(), alias.getValue(), CriteriaSpecification.LEFT_JOIN);
        }
        return detachedCriteria;
    }

    public void add(Criterion... criterions) {
        for (Criterion criterion : criterions) {
            detachedCriteria.add(criterion);
        }
    }

    public void addOrder(Order... orders) {
        for (Order order : orders) {
            detachedCriteria.addOrder(order);
        }
    }

    /* *************
     *
     * *************/
    protected String aliasFor(String propertyPath) {
        String rootAlias = detachedCriteria.getAlias();
        if (propertyPath.startsWith(rootAlias + ".")) {
            propertyPath = propertyPath.substring(rootAlias.length() + 1);
        }

        // parent alias
        String alias = rootAlias;
        String[] pair = propertyPath.split("\\.");

        // create path alias for property
        for (int i = 0; i < pair.length - 1; i++) {
            propertyPath = alias + "." + pair[i];
            alias = allAlias.get(propertyPath);

            if (alias != null) {
                continue;
            }

            alias = propertyPath.replace('.', '_');
            allAlias.put(propertyPath, alias);
        }

        return alias + "." + pair[pair.length - 1];
    }

    /**
     * 根据给定 property, operator, value 构建 Hibernate Criterion
     *
     * @param property
     * @param operator
     * @param value
     * @return
     */
    protected Criterion buildCriterion(String property, Filters.Operator operator, Object value) {
        Criterion criterion = null;
        switch (operator) {
            case EQ:
                criterion = Restrictions.eq(property, value);
                break;
            case NE:
                criterion = Restrictions.ne(property, value);
                break;
            case GT:
                criterion = Restrictions.gt(property, value);
                break;
            case LT:
                criterion = Restrictions.lt(property, value);
                break;
            case GE:
                criterion = Restrictions.ge(property, value);
                break;
            case LE:
                criterion = Restrictions.le(property, value);
                break;
            case LK:
                // TODO 这里默认是忽略大小写
                criterion = Restrictions.ilike(property, value);
                break;
            // UPDATE 2015.01.17 根据 jqGrid 增加
            case NU: // null
                criterion = Restrictions.isNull(property);
                break;
            case NN:
                criterion = Restrictions.isNotNull(property);
                break;
            case BW:
            case EW:
            case CN:
                criterion = Restrictions.ilike(property, value);
                break;
            case BN:
            case EN:
            case NC:
                criterion = Restrictions.not(Restrictions.ilike(property, value));
            default:
                ;
        }

        return criterion;
    }

    public DetachedCriteria getDetachedCriteria() {
        return detachedCriteria;
    }

    public Map<String, String> getAllAlias() {
        return allAlias;
    }
}
